home *** CD-ROM | disk | FTP | other *** search
/ Clickx 53 / Clickx 53.iso / software / onmisbaretool / firefoxv301 / Firefox Setup 3.0.1.exe / nonlocalized / components / nsSessionStore.js < prev    next >
Encoding:
JavaScript  |  2008-07-02  |  74.0 KB  |  2,161 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is the nsSessionStore component.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Simon B├╝nzli <zeniko@gmail.com>
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Dietrich Ayala <dietrich@mozilla.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. /**
  39.  * Session Storage and Restoration
  40.  * 
  41.  * Overview
  42.  * This service keeps track of a user's session, storing the various bits
  43.  * required to return the browser to it's current state. The relevant data is 
  44.  * stored in memory, and is periodically saved to disk in a file in the 
  45.  * profile directory. The service is started at first window load, in
  46.  * delayedStartup, and will restore the session from the data received from
  47.  * the nsSessionStartup service.
  48.  */
  49.  
  50. /* :::::::: Constants and Helpers ::::::::::::::: */
  51.  
  52. const Cc = Components.classes;
  53. const Ci = Components.interfaces;
  54. const Cr = Components.results;
  55. const Cu = Components.utils;
  56.  
  57. const STATE_STOPPED = 0;
  58. const STATE_RUNNING = 1;
  59. const STATE_QUITTING = -1;
  60. const STATE_DISABLED = -2;
  61.  
  62. const STATE_STOPPED_STR = "stopped";
  63. const STATE_RUNNING_STR = "running";
  64.  
  65. const PRIVACY_NONE = 0;
  66. const PRIVACY_ENCRYPTED = 1;
  67. const PRIVACY_FULL = 2;
  68.  
  69. const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
  70.  
  71. // global notifications observed
  72. const OBSERVING = [
  73.   "domwindowopened", "domwindowclosed",
  74.   "quit-application-requested", "quit-application-granted",
  75.   "quit-application", "browser:purge-session-history"
  76. ];
  77.  
  78. /*
  79. XUL Window properties to (re)store
  80. Restored in restoreDimensions()
  81. */
  82. const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
  83.  
  84. /* 
  85. Hideable window features to (re)store
  86. Restored in restoreWindowFeatures()
  87. */
  88. const WINDOW_HIDEABLE_FEATURES = [
  89.   "menubar", "toolbar", "locationbar", 
  90.   "personalbar", "statusbar", "scrollbars"
  91. ];
  92.  
  93. /*
  94. docShell capabilities to (re)store
  95. Restored in restoreHistory()
  96. eg: browser.docShell["allow" + aCapability] = false;
  97. */
  98. const CAPABILITIES = [
  99.   "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
  100. ];
  101.  
  102. // module for JSON conversion (needed for the nsISessionStore API)
  103. Cu.import("resource://gre/modules/JSON.jsm");
  104. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  105.  
  106. function debug(aMsg) {
  107.   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
  108.   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
  109.                                      .logStringMessage(aMsg);
  110. }
  111.  
  112. /* :::::::: The Service ::::::::::::::: */
  113.  
  114. function SessionStoreService() {
  115. }
  116.  
  117. SessionStoreService.prototype = {
  118.   classDescription: "Browser Session Store Service",
  119.   contractID: "@mozilla.org/browser/sessionstore;1",
  120.   classID: Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}"),
  121.   QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
  122.                                          Ci.nsIDOMEventListener,
  123.                                          Ci.nsIObserver,
  124.                                          Ci.nsISupportsWeakReference]),
  125.  
  126.   // xul:tab attributes to (re)store (extensions might want to hook in here)
  127.   xulAttributes: [],
  128.  
  129.   // set default load state
  130.   _loadState: STATE_STOPPED,
  131.  
  132.   // minimal interval between two save operations (in milliseconds)
  133.   _interval: 10000,
  134.  
  135.   // when crash recovery is disabled, session data is not written to disk
  136.   _resume_from_crash: true,
  137.  
  138.   // During the initial restore tracks the number of windows yet to be restored
  139.   _restoreCount: 0,
  140.  
  141.   // time in milliseconds (Date.now()) when the session was last written to file
  142.   _lastSaveTime: 0, 
  143.  
  144.   // states for all currently opened windows
  145.   _windows: {},
  146.  
  147.   // in case the last closed window ain't a navigator:browser one
  148.   _lastWindowClosed: null,
  149.  
  150.   // not-"dirty" windows usually don't need to have their data updated
  151.   _dirtyWindows: {},
  152.  
  153.   // flag all windows as dirty
  154.   _dirty: false,
  155.  
  156. /* ........ Global Event Handlers .............. */
  157.  
  158.   /**
  159.    * Initialize the component
  160.    */
  161.   init: function sss_init(aWindow) {
  162.     if (this._loadState == STATE_DISABLED)
  163.       return;
  164.  
  165.     if (!aWindow || this._loadState == STATE_RUNNING) {
  166.       // make sure that all browser windows which try to initialize
  167.       // SessionStore are really tracked by it
  168.       if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  169.         this.onLoad(aWindow);
  170.       return;
  171.     }
  172.  
  173.     this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  174.                        getService(Ci.nsIPrefService).getBranch("browser.");
  175.     this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  176.  
  177.     var observerService = Cc["@mozilla.org/observer-service;1"].
  178.                           getService(Ci.nsIObserverService);
  179.  
  180.     // if the service is disabled, do not init 
  181.     if (!this._prefBranch.getBoolPref("sessionstore.enabled")) {
  182.       // Notify observers that the sessionstore has done everything it is going to.
  183.       observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  184.       // Mark as disabled so we don't even try to initialise again.
  185.       this._loadState = STATE_DISABLED;
  186.       return;
  187.     }
  188.  
  189.     OBSERVING.forEach(function(aTopic) {
  190.       observerService.addObserver(this, aTopic, true);
  191.     }, this);
  192.     
  193.     // get interval from prefs - used often, so caching/observing instead of fetching on-demand
  194.     this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  195.     this._prefBranch.addObserver("sessionstore.interval", this, true);
  196.     
  197.     // get crash recovery state from prefs and allow for proper reaction to state changes
  198.     this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  199.     this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
  200.     
  201.     // observe prefs changes so we can modify stored data to match
  202.     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
  203.  
  204.     // get file references
  205.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  206.                      getService(Ci.nsIProperties);
  207.     this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
  208.     this._sessionFileBackup = this._sessionFile.clone();
  209.     this._sessionFile.append("sessionstore.js");
  210.     this._sessionFileBackup.append("sessionstore.bak");
  211.    
  212.     // get string containing session state
  213.     var iniString;
  214.     try {
  215.       var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
  216.                getService(Ci.nsISessionStartup);
  217.       if (ss.doRestore())
  218.         iniString = ss.state;
  219.     }
  220.     catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
  221.  
  222.     if (iniString) {
  223.       try {
  224.         // parse the session state into JS objects
  225.         this._initialState = this._safeEval(iniString);
  226.         // set bool detecting crash
  227.         this._lastSessionCrashed =
  228.           this._initialState.session && this._initialState.session.state &&
  229.           this._initialState.session.state == STATE_RUNNING_STR;
  230.         
  231.         // restore the features of the first window from localstore.rdf
  232.         WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  233.           delete this._initialState.windows[0][aAttr];
  234.         }, this);
  235.         delete this._initialState.windows[0].hidden;
  236.       }
  237.       catch (ex) { debug("The session file is invalid: " + ex); }
  238.     }
  239.     
  240.     // if last session crashed, backup the session
  241.     if (this._lastSessionCrashed) {
  242.       try {
  243.         this._writeFile(this._sessionFileBackup, iniString);
  244.       }
  245.       catch (ex) { } // nothing else we can do here
  246.     }
  247.  
  248.     // remove the session data files if crash recovery is disabled
  249.     if (!this._resume_from_crash)
  250.       this._clearDisk();
  251.     
  252.     // As this is called at delayedStartup, restoration must be initiated here
  253.     this.onLoad(aWindow);
  254.   },
  255.  
  256.   /**
  257.    * Called on application shutdown, after notifications:
  258.    * quit-application-granted, quit-application
  259.    */
  260.   _uninit: function sss_uninit() {
  261.     if (this._doResumeSession()) { // save all data for session resuming 
  262.       this.saveState(true);
  263.     }
  264.     else { // discard all session related data 
  265.       this._clearDisk();
  266.     }
  267.     // Make sure to break our cycle with the save timer
  268.     if (this._saveTimer) {
  269.       this._saveTimer.cancel();
  270.       this._saveTimer = null;
  271.     }
  272.   },
  273.  
  274.   /**
  275.    * Handle notifications
  276.    */
  277.   observe: function sss_observe(aSubject, aTopic, aData) {
  278.     // for event listeners
  279.     var _this = this;
  280.  
  281.     switch (aTopic) {
  282.     case "domwindowopened": // catch new windows
  283.       aSubject.addEventListener("load", function(aEvent) {
  284.         aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
  285.         _this.onLoad(aEvent.currentTarget);
  286.         }, false);
  287.       break;
  288.     case "domwindowclosed": // catch closed windows
  289.       this.onClose(aSubject);
  290.       break;
  291.     case "quit-application-requested":
  292.       // get a current snapshot of all windows
  293.       this._forEachBrowserWindow(function(aWindow) {
  294.         this._collectWindowData(aWindow);
  295.       });
  296.       this._dirtyWindows = [];
  297.       this._dirty = false;
  298.       break;
  299.     case "quit-application-granted":
  300.       // freeze the data at what we've got (ignoring closing windows)
  301.       this._loadState = STATE_QUITTING;
  302.       break;
  303.     case "quit-application":
  304.       if (aData == "restart")
  305.         this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
  306.       this._loadState = STATE_QUITTING; // just to be sure
  307.       this._uninit();
  308.       break;
  309.     case "browser:purge-session-history": // catch sanitization 
  310.       this._forEachBrowserWindow(function(aWindow) {
  311.         Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) {
  312.           delete aBrowser.parentNode.__SS_data;
  313.         });
  314.       });
  315.       this._lastWindowClosed = null;
  316.       this._clearDisk();
  317.       // also clear all data about closed tabs
  318.       for (ix in this._windows) {
  319.         this._windows[ix]._closedTabs = [];
  320.       }
  321.       // give the tabbrowsers a chance to clear their histories first
  322.       var win = this._getMostRecentBrowserWindow();
  323.       if (win)
  324.         win.setTimeout(function() { _this.saveState(true); }, 0);
  325.       else
  326.         this.saveState(true);
  327.       break;
  328.     case "nsPref:changed": // catch pref changes
  329.       switch (aData) {
  330.       // if the user decreases the max number of closed tabs they want
  331.       // preserved update our internal states to match that max
  332.       case "sessionstore.max_tabs_undo":
  333.         var ix;
  334.         for (ix in this._windows) {
  335.           this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
  336.         }
  337.         break;
  338.       case "sessionstore.interval":
  339.         this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  340.         // reset timer and save
  341.         if (this._saveTimer) {
  342.           this._saveTimer.cancel();
  343.           this._saveTimer = null;
  344.         }
  345.         this.saveStateDelayed(null, -1);
  346.         break;
  347.       case "sessionstore.resume_from_crash":
  348.         this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  349.         // either create the file with crash recovery information or remove it
  350.         // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
  351.         if (this._resume_from_crash)
  352.           this.saveState(true);
  353.         else if (this._loadState == STATE_RUNNING)
  354.           this._clearDisk();
  355.         break;
  356.       }
  357.       break;
  358.     case "timer-callback": // timer call back for delayed saving
  359.       this._saveTimer = null;
  360.       this.saveState();
  361.       break;
  362.     }
  363.   },
  364.  
  365. /* ........ Window Event Handlers .............. */
  366.  
  367.   /**
  368.    * Implement nsIDOMEventListener for handling various window and tab events
  369.    */
  370.   handleEvent: function sss_handleEvent(aEvent) {
  371.     switch (aEvent.type) {
  372.       case "load":
  373.       case "pageshow":
  374.         this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  375.         break;
  376.       case "input":
  377.       case "DOMAutoComplete":
  378.         this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  379.         break;
  380.       case "TabOpen":
  381.       case "TabClose":
  382.         var panelID = aEvent.originalTarget.linkedPanel;
  383.         var tabpanel = aEvent.originalTarget.ownerDocument.getElementById(panelID);
  384.         if (aEvent.type == "TabOpen") {
  385.           this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  386.         }
  387.         else {
  388.           this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget);
  389.           this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  390.         }
  391.         break;
  392.       case "TabSelect":
  393.         var tabpanels = aEvent.currentTarget.mPanelContainer;
  394.         this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels);
  395.         break;
  396.     }
  397.   },
  398.  
  399.   /**
  400.    * If it's the first window load since app start...
  401.    * - determine if we're reloading after a crash or a forced-restart
  402.    * - restore window state
  403.    * - restart downloads
  404.    * Set up event listeners for this window's tabs
  405.    * @param aWindow
  406.    *        Window reference
  407.    */
  408.   onLoad: function sss_onLoad(aWindow) {
  409.     // return if window has already been initialized
  410.     if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
  411.       return;
  412.  
  413.     // ignore non-browser windows and windows opened while shutting down
  414.     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
  415.       this._loadState == STATE_QUITTING)
  416.       return;
  417.  
  418.     // assign it a unique identifier (timestamp)
  419.     aWindow.__SSi = "window" + Date.now();
  420.  
  421.     // and create its data object
  422.     this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
  423.     
  424.     // perform additional initialization when the first window is loading
  425.     if (this._loadState == STATE_STOPPED) {
  426.       this._loadState = STATE_RUNNING;
  427.       this._lastSaveTime = Date.now();
  428.       
  429.       // don't save during the first ten seconds
  430.       // (until most of the pages have been restored)
  431.       this.saveStateDelayed(aWindow, 10000);
  432.  
  433.       // restore a crashed session resp. resume the last session if requested
  434.       if (this._initialState) {
  435.         // make sure that the restored tabs are first in the window
  436.         this._initialState._firstTabs = true;
  437.         this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
  438.         this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow));
  439.         delete this._initialState;
  440.       }
  441.       else {
  442.         // Nothing to restore, notify observers things are complete.
  443.         var observerService = Cc["@mozilla.org/observer-service;1"].
  444.                               getService(Ci.nsIObserverService);
  445.         observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  446.       }
  447.     }
  448.     
  449.     var tabbrowser = aWindow.getBrowser();
  450.     var tabpanels = tabbrowser.mPanelContainer;
  451.     
  452.     // add tab change listeners to all already existing tabs
  453.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  454.       this.onTabAdd(aWindow, tabpanels.childNodes[i], true);
  455.     }
  456.     // notification of tab add/remove/selection
  457.     tabbrowser.addEventListener("TabOpen", this, true);
  458.     tabbrowser.addEventListener("TabClose", this, true);
  459.     tabbrowser.addEventListener("TabSelect", this, true);
  460.   },
  461.  
  462.   /**
  463.    * On window close...
  464.    * - remove event listeners from tabs
  465.    * - save all window data
  466.    * @param aWindow
  467.    *        Window reference
  468.    */
  469.   onClose: function sss_onClose(aWindow) {
  470.     // ignore windows not tracked by SessionStore
  471.     if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
  472.       return;
  473.     }
  474.     
  475.     if (this.windowToFocus && this.windowToFocus == aWindow) {
  476.       delete this.windowToFocus;
  477.     }
  478.     
  479.     var tabbrowser = aWindow.getBrowser();
  480.     var tabpanels = tabbrowser.mPanelContainer;
  481.  
  482.     tabbrowser.removeEventListener("TabOpen", this, true);
  483.     tabbrowser.removeEventListener("TabClose", this, true);
  484.     tabbrowser.removeEventListener("TabSelect", this, true);
  485.     
  486.     if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down 
  487.       // update all window data for a last time
  488.       this._collectWindowData(aWindow);
  489.       
  490.       // preserve this window's data (in case it was the last navigator:browser)
  491.       this._lastWindowClosed = this._windows[aWindow.__SSi];
  492.       this._lastWindowClosed.title = aWindow.content.document.title;
  493.       this._updateCookies([this._lastWindowClosed]);
  494.       
  495.       // clear this window from the list
  496.       delete this._windows[aWindow.__SSi];
  497.       
  498.       // save the state without this window to disk
  499.       this.saveStateDelayed();
  500.     }
  501.     
  502.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  503.       this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
  504.     }
  505.     
  506.     // cache the window state until the window is completely gone
  507.     aWindow.__SS_dyingCache = this._windows[aWindow.__SSi] || this._lastWindowClosed;
  508.     
  509.     // reset the _tab property to avoid keeping the tab's XUL element alive
  510.     // longer than we need it
  511.     var tabCount = aWindow.__SS_dyingCache.tabs.length;
  512.     for (var t = 0; t < tabCount; t++) {
  513.       delete aWindow.__SS_dyingCache.tabs[t]._tab;
  514.     }
  515.     
  516.     delete aWindow.__SSi;
  517.   },
  518.  
  519.   /**
  520.    * set up listeners for a new tab
  521.    * @param aWindow
  522.    *        Window reference
  523.    * @param aPanel
  524.    *        TabPanel reference
  525.    * @param aNoNotification
  526.    *        bool Do not save state if we're updating an existing tab
  527.    */
  528.   onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) {
  529.     aPanel.addEventListener("load", this, true);
  530.     aPanel.addEventListener("pageshow", this, true);
  531.     aPanel.addEventListener("input", this, true);
  532.     aPanel.addEventListener("DOMAutoComplete", this, true);
  533.     
  534.     if (!aNoNotification) {
  535.       this.saveStateDelayed(aWindow);
  536.     }
  537.   },
  538.  
  539.   /**
  540.    * remove listeners for a tab
  541.    * @param aWindow
  542.    *        Window reference
  543.    * @param aPanel
  544.    *        TabPanel reference
  545.    * @param aNoNotification
  546.    *        bool Do not save state if we're updating an existing tab
  547.    */
  548.   onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) {
  549.     aPanel.removeEventListener("load", this, true);
  550.     aPanel.removeEventListener("pageshow", this, true);
  551.     aPanel.removeEventListener("input", this, true);
  552.     aPanel.removeEventListener("DOMAutoComplete", this, true);
  553.     
  554.     delete aPanel.__SS_data;
  555.     delete aPanel.__SS_text;
  556.     
  557.     if (!aNoNotification) {
  558.       this.saveStateDelayed(aWindow);
  559.     }
  560.   },
  561.  
  562.   /**
  563.    * When a tab closes, collect it's properties
  564.    * @param aWindow
  565.    *        Window reference
  566.    * @param aTab
  567.    *        TabPanel reference
  568.    */
  569.   onTabClose: function sss_onTabClose(aWindow, aTab) {
  570.     // notify the tabbrowser that the tab state will be retrieved for the last time
  571.     // (so that extension authors can easily set data on soon-to-be-closed tabs)
  572.     var event = aWindow.document.createEvent("Events");
  573.     event.initEvent("SSTabClosing", true, false);
  574.     aTab.dispatchEvent(event);
  575.     
  576.     var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
  577.     // don't update our internal state if we don't have to
  578.     if (maxTabsUndo == 0) {
  579.       return;
  580.     }
  581.     
  582.     // make sure that the tab related data is up-to-date
  583.     var tabState = this._collectTabData(aTab);
  584.     this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
  585.  
  586.     // reset the _tab property to avoid keeping the tab's XUL element alive
  587.     // longer than we need it
  588.     delete tabState._tab;
  589.     
  590.     // store closed-tab data for undo
  591.     if (tabState.entries.length > 1 || tabState.entries[0].url != "about:blank") {
  592.       this._windows[aWindow.__SSi]._closedTabs.unshift({
  593.         state: tabState,
  594.         title: aTab.getAttribute("label"),
  595.         image: aTab.getAttribute("image"),
  596.         pos: aTab._tPos
  597.       });
  598.       var length = this._windows[aWindow.__SSi]._closedTabs.length;
  599.       if (length > maxTabsUndo)
  600.         this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo);
  601.     }
  602.   },
  603.  
  604.   /**
  605.    * When a tab loads, save state.
  606.    * @param aWindow
  607.    *        Window reference
  608.    * @param aPanel
  609.    *        TabPanel reference
  610.    * @param aEvent
  611.    *        Event obj
  612.    */
  613.   onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) { 
  614.     // react on "load" and solitary "pageshow" events (the first "pageshow"
  615.     // following "load" is too late for deleting the data caches)
  616.     if (aEvent.type != "load" && !aEvent.persisted) {
  617.       return;
  618.     }
  619.     
  620.     delete aPanel.__SS_data;
  621.     delete aPanel.__SS_text;
  622.     this.saveStateDelayed(aWindow);
  623.     
  624.     // attempt to update the current URL we send in a crash report
  625.     this._updateCrashReportURL(aWindow);
  626.   },
  627.  
  628.   /**
  629.    * Called when a tabpanel sends the "input" notification 
  630.    * stores textarea data
  631.    * @param aWindow
  632.    *        Window reference
  633.    * @param aPanel
  634.    *        TabPanel reference
  635.    * @param aEvent
  636.    *        Event obj
  637.    */
  638.   onTabInput: function sss_onTabInput(aWindow, aPanel, aEvent) {
  639.     if (this._saveTextData(aPanel, aEvent.originalTarget)) {
  640.       this.saveStateDelayed(aWindow, 3000);
  641.     }
  642.   },
  643.  
  644.   /**
  645.    * When a tab is selected, save session data
  646.    * @param aWindow
  647.    *        Window reference
  648.    * @param aPanels
  649.    *        TabPanel reference
  650.    */
  651.   onTabSelect: function sss_onTabSelect(aWindow, aPanels) {
  652.     if (this._loadState == STATE_RUNNING) {
  653.       this._windows[aWindow.__SSi].selected = aPanels.selectedIndex;
  654.       this.saveStateDelayed(aWindow);
  655.  
  656.       // attempt to update the current URL we send in a crash report
  657.       this._updateCrashReportURL(aWindow);
  658.     }
  659.   },
  660.  
  661. /* ........ nsISessionStore API .............. */
  662.  
  663.   getBrowserState: function sss_getBrowserState() {
  664.     return this._toJSONString(this._getCurrentState());
  665.   },
  666.  
  667.   setBrowserState: function sss_setBrowserState(aState) {
  668.     var window = this._getMostRecentBrowserWindow();
  669.     if (!window) {
  670.       this._openWindowWithState("(" + aState + ")");
  671.       return;
  672.     }
  673.  
  674.     // close all other browser windows
  675.     this._forEachBrowserWindow(function(aWindow) {
  676.       if (aWindow != window) {
  677.         aWindow.close();
  678.       }
  679.     });
  680.  
  681.     // restore to the given state
  682.     this.restoreWindow(window, "(" + aState + ")", true);
  683.   },
  684.  
  685.   getWindowState: function sss_getWindowState(aWindow) {
  686.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  687.       return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
  688.     
  689.     return this._toJSONString(this._getWindowState(aWindow));
  690.   },
  691.  
  692.   setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
  693.     this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
  694.   },
  695.  
  696.   getTabState: function sss_getTabState(aTab) {
  697.     var tabState = this._collectTabData(aTab);
  698.     
  699.     var window = aTab.ownerDocument.defaultView;
  700.     this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
  701.     
  702.     return this._toJSONString(tabState);
  703.   },
  704.  
  705.   setTabState: function sss_setTabState(aTab, aState) {
  706.     var tabState = this._safeEval("(" + aState + ")");
  707.     if (!tabState.entries || !tabState.entries.length) {
  708.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  709.       return;
  710.     }
  711.     tabState._tab = aTab;
  712.     
  713.     var window = aTab.ownerDocument.defaultView;
  714.     this.restoreHistoryPrecursor(window, [tabState], 0, 0, 0);
  715.   },
  716.  
  717.   duplicateTab: function sss_duplicateTab(aWindow, aTab) {
  718.     var tabState = this._collectTabData(aTab, true);
  719.     var sourceWindow = aTab.ownerDocument.defaultView;
  720.     this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
  721.     
  722.     var newTab = aWindow.getBrowser().addTab();
  723.     tabState._tab = newTab;
  724.     this.restoreHistoryPrecursor(aWindow, [tabState], 0, 0, 0);
  725.     
  726.     return newTab;
  727.   },
  728.  
  729.   getClosedTabCount: function sss_getClosedTabCount(aWindow) {
  730.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  731.       return aWindow.__SS_dyingCache._closedTabs.length;
  732.     if (!aWindow.__SSi)
  733.       return 0; // not a browser window, or not otherwise tracked by SS.
  734.     
  735.     return this._windows[aWindow.__SSi]._closedTabs.length;
  736.   },
  737.  
  738.   closedTabNameAt: function sss_closedTabNameAt(aWindow, aIx) {
  739.     var tabs;
  740.     
  741.     if (aWindow.__SSi && aWindow.__SSi in this._windows)
  742.       tabs = this._windows[aWindow.__SSi]._closedTabs;
  743.     else if (aWindow.__SS_dyingCache)
  744.       tabs = aWindow.__SS_dyingCache._closedTabs;
  745.     else
  746.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  747.     
  748.     return tabs && aIx in tabs ? tabs[aIx].title : null;
  749.   },
  750.  
  751.   getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
  752.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  753.       return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
  754.     
  755.     return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
  756.   },
  757.  
  758.   undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
  759.     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
  760.  
  761.     // default to the most-recently closed tab
  762.     aIndex = aIndex || 0;
  763.  
  764.     if (aIndex in closedTabs) {
  765.       var browser = aWindow.getBrowser();
  766.  
  767.       // fetch the data of closed tab, while removing it from the array
  768.       var closedTab = closedTabs.splice(aIndex, 1).shift();
  769.       var closedTabState = closedTab.state;
  770.  
  771.       // create a new tab
  772.       closedTabState._tab = browser.addTab();
  773.         
  774.       // restore the tab's position
  775.       browser.moveTabTo(closedTabState._tab, closedTab.pos);
  776.   
  777.       // restore tab content
  778.       this.restoreHistoryPrecursor(aWindow, [closedTabState], 1, 0, 0);
  779.  
  780.       // focus the tab's content area
  781.       var content = browser.getBrowserForTab(closedTabState._tab).contentWindow;
  782.       aWindow.setTimeout(function() { content.focus(); }, 0);
  783.     }
  784.     else {
  785.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  786.     }
  787.   },
  788.  
  789.   getWindowValue: function sss_getWindowValue(aWindow, aKey) {
  790.     if (aWindow.__SSi) {
  791.       var data = this._windows[aWindow.__SSi].extData || {};
  792.       return data[aKey] || "";
  793.     }
  794.     else if (aWindow.__SS_dyingCache) {
  795.       data = aWindow.__SS_dyingCache.extData || {};
  796.       return data[aKey] || "";
  797.     }
  798.     else {
  799.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  800.     }
  801.   },
  802.  
  803.   setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) {
  804.     if (aWindow.__SSi) {
  805.       if (!this._windows[aWindow.__SSi].extData) {
  806.         this._windows[aWindow.__SSi].extData = {};
  807.       }
  808.       this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
  809.       this.saveStateDelayed(aWindow);
  810.     }
  811.     else {
  812.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  813.     }
  814.   },
  815.  
  816.   deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) {
  817.     if (this._windows[aWindow.__SSi].extData[aKey])
  818.       delete this._windows[aWindow.__SSi].extData[aKey];
  819.     else
  820.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  821.   },
  822.  
  823.   getTabValue: function sss_getTabValue(aTab, aKey) {
  824.     var data = aTab.__SS_extdata || {};
  825.     return data[aKey] || "";
  826.   },
  827.  
  828.   setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) {
  829.     if (!aTab.__SS_extdata) {
  830.       aTab.__SS_extdata = {};
  831.     }
  832.     aTab.__SS_extdata[aKey] = aStringValue;
  833.     this.saveStateDelayed(aTab.ownerDocument.defaultView);
  834.   },
  835.  
  836.   deleteTabValue: function sss_deleteTabValue(aTab, aKey) {
  837.     if (aTab.__SS_extdata[aKey])
  838.       delete aTab.__SS_extdata[aKey];
  839.     else
  840.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  841.   },
  842.  
  843.  
  844.   persistTabAttribute: function sss_persistTabAttribute(aName) {
  845.     this.xulAttributes.push(aName);
  846.     this.saveStateDelayed();
  847.   },
  848.  
  849. /* ........ Saving Functionality .............. */
  850.  
  851.   /**
  852.    * Store all session data for a window
  853.    * @param aWindow
  854.    *        Window reference
  855.    */
  856.   _saveWindowHistory: function sss_saveWindowHistory(aWindow) {
  857.     var tabbrowser = aWindow.getBrowser();
  858.     var tabs = tabbrowser.mTabs;
  859.     var tabsData = this._windows[aWindow.__SSi].tabs = [];
  860.     
  861.     for (var i = 0; i < tabs.length; i++)
  862.       tabsData.push(this._collectTabData(tabs[i]));
  863.     
  864.     this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
  865.   },
  866.  
  867.   /**
  868.    * Collect data related to a single tab
  869.    * @param aTab
  870.    *        tabbrowser tab
  871.    * @param aFullData
  872.    *        always return privacy sensitive data (use with care)
  873.    * @returns object
  874.    */
  875.   _collectTabData: function sss_collectTabData(aTab, aFullData) {
  876.     var tabData = { entries: [], index: 0 };
  877.     var browser = aTab.linkedBrowser;
  878.     
  879.     if (!browser || !browser.currentURI)
  880.       // can happen when calling this function right after .addTab()
  881.       return tabData;
  882.     else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tab)
  883.       // use the data to be restored when the tab hasn't been completely loaded
  884.       return browser.parentNode.__SS_data;
  885.     
  886.     var history = null;
  887.     try {
  888.       history = browser.sessionHistory;
  889.     }
  890.     catch (ex) { } // this could happen if we catch a tab during (de)initialization
  891.     
  892.     if (history && browser.parentNode.__SS_data &&
  893.         browser.parentNode.__SS_data.entries[history.index] && !aFullData) {
  894.       tabData = browser.parentNode.__SS_data;
  895.       tabData.index = history.index + 1;
  896.     }
  897.     else if (history && history.count > 0) {
  898.       for (var j = 0; j < history.count; j++)
  899.         tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
  900.                                                          aFullData));
  901.       tabData.index = history.index + 1;
  902.  
  903.       // make sure not to cache privacy sensitive data which shouldn't get out
  904.       if (!aFullData)
  905.         browser.parentNode.__SS_data = tabData;
  906.     }
  907.     else {
  908.       tabData.entries[0] = { url: browser.currentURI.spec };
  909.       tabData.index = 1;
  910.     }
  911.     
  912.     var disallow = [];
  913.     for (var i = 0; i < CAPABILITIES.length; i++)
  914.       if (!browser.docShell["allow" + CAPABILITIES[i]])
  915.         disallow.push(CAPABILITIES[i]);
  916.     if (disallow.length > 0)
  917.       tabData.disallow = disallow.join(",");
  918.     else if (tabData.disallow)
  919.       delete tabData.disallow;
  920.     
  921.     if (this.xulAttributes.length > 0) {
  922.       var xulattr = Array.filter(aTab.attributes, function(aAttr) {
  923.         return this.xulAttributes.indexOf(aAttr.name) > -1;
  924.       }, this).map(function(aAttr) {
  925.         return aAttr.name + "=" + encodeURI(aAttr.value);
  926.       });
  927.       tabData.xultab = xulattr.join(" ");
  928.     }
  929.     
  930.     if (aTab.__SS_extdata)
  931.       tabData.extData = aTab.__SS_extdata;
  932.     else if (tabData.extData)
  933.       delete tabData.extData;
  934.     
  935.     return tabData;
  936.   },
  937.  
  938.   /**
  939.    * Get an object that is a serialized representation of a History entry
  940.    * Used for data storage
  941.    * @param aEntry
  942.    *        nsISHEntry instance
  943.    * @param aFullData
  944.    *        always return privacy sensitive data (use with care)
  945.    * @returns object
  946.    */
  947.   _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
  948.     var entry = { url: aEntry.URI.spec };
  949.     
  950.     if (aEntry.title && aEntry.title != entry.url) {
  951.       entry.title = aEntry.title;
  952.     }
  953.     if (aEntry.isSubFrame) {
  954.       entry.subframe = true;
  955.     }
  956.     if (!(aEntry instanceof Ci.nsISHEntry)) {
  957.       return entry;
  958.     }
  959.     
  960.     var cacheKey = aEntry.cacheKey;
  961.     if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
  962.         cacheKey.data != 0) {
  963.       // XXXbz would be better to have cache keys implement
  964.       // nsISerializable or something.
  965.       entry.cacheKey = cacheKey.data;
  966.     }
  967.     entry.ID = aEntry.ID;
  968.     
  969.     if (aEntry.contentType)
  970.       entry.contentType = aEntry.contentType;
  971.     
  972.     var x = {}, y = {};
  973.     aEntry.getScrollPosition(x, y);
  974.     if (x.value != 0 || y.value != 0)
  975.       entry.scroll = x.value + "," + y.value;
  976.     
  977.     try {
  978.       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
  979.       if (aEntry.postData && (aFullData ||
  980.             prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
  981.         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
  982.                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
  983.         var stream = Cc["@mozilla.org/binaryinputstream;1"].
  984.                      createInstance(Ci.nsIBinaryInputStream);
  985.         stream.setInputStream(aEntry.postData);
  986.         var postBytes = stream.readByteArray(stream.available());
  987.         var postdata = String.fromCharCode.apply(null, postBytes);
  988.         if (aFullData || prefPostdata == -1 ||
  989.             postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
  990.               prefPostdata) {
  991.           // We can stop doing base64 encoding once our serialization into JSON
  992.           // is guaranteed to handle all chars in strings, including embedded
  993.           // nulls.
  994.           entry.postdata_b64 = btoa(postdata);
  995.         }
  996.       }
  997.     }
  998.     catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
  999.  
  1000.     if (aEntry.owner) {
  1001.       // Not catching anything specific here, just possible errors
  1002.       // from writeCompoundObject and the like.
  1003.       try {
  1004.         var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
  1005.                            createInstance(Ci.nsIObjectOutputStream);
  1006.         var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
  1007.         pipe.init(false, false, 0, 0xffffffff, null);
  1008.         binaryStream.setOutputStream(pipe.outputStream);
  1009.         binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
  1010.         binaryStream.close();
  1011.  
  1012.         // Now we want to read the data from the pipe's input end and encode it.
  1013.         var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
  1014.                                createInstance(Ci.nsIBinaryInputStream);
  1015.         scriptableStream.setInputStream(pipe.inputStream);
  1016.         var ownerBytes =
  1017.           scriptableStream.readByteArray(scriptableStream.available());
  1018.         // We can stop doing base64 encoding once our serialization into JSON
  1019.         // is guaranteed to handle all chars in strings, including embedded
  1020.         // nulls.
  1021.         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
  1022.       }
  1023.       catch (ex) { debug(ex); }
  1024.     }
  1025.     
  1026.     if (!(aEntry instanceof Ci.nsISHContainer)) {
  1027.       return entry;
  1028.     }
  1029.     
  1030.     if (aEntry.childCount > 0) {
  1031.       entry.children = [];
  1032.       for (var i = 0; i < aEntry.childCount; i++) {
  1033.         var child = aEntry.GetChildAt(i);
  1034.         if (child) {
  1035.           entry.children.push(this._serializeHistoryEntry(child, aFullData));
  1036.         }
  1037.         else { // to maintain the correct frame order, insert a dummy entry 
  1038.           entry.children.push({ url: "about:blank" });
  1039.         }
  1040.       }
  1041.     }
  1042.     
  1043.     return entry;
  1044.   },
  1045.  
  1046.   /**
  1047.    * Updates the current document's cache of user entered text data
  1048.    * @param aPanel
  1049.    *        TabPanel reference
  1050.    * @param aTextarea
  1051.    *        HTML content element
  1052.    * @returns bool
  1053.    */
  1054.   _saveTextData: function sss_saveTextData(aPanel, aTextarea) {
  1055.     var id = aTextarea.id ? "#" + aTextarea.id :
  1056.                             aTextarea.name;
  1057.     if (!id
  1058.       || !(aTextarea instanceof Ci.nsIDOMHTMLTextAreaElement 
  1059.       || aTextarea instanceof Ci.nsIDOMHTMLInputElement)) {
  1060.       return false; // nothing to save
  1061.     }
  1062.     
  1063.     if (!aPanel.__SS_text) {
  1064.       aPanel.__SS_text = [];
  1065.       aPanel.__SS_text._refs = [];
  1066.     }
  1067.     
  1068.     // get the index of the reference to the text element
  1069.     var ix = aPanel.__SS_text._refs.indexOf(aTextarea);
  1070.     if (ix == -1) {
  1071.       // we haven't registered this text element yet - do so now
  1072.       aPanel.__SS_text._refs.push(aTextarea);
  1073.       ix = aPanel.__SS_text.length;
  1074.     }
  1075.     else if (!aPanel.__SS_text[ix].cache) {
  1076.       // we've already marked this text element for saving (the cache is
  1077.       // added during save operations and would have to be updated here)
  1078.       return false;
  1079.     }
  1080.     
  1081.     // determine the frame we're in and encode it into the textarea's ID
  1082.     var content = aTextarea.ownerDocument.defaultView;
  1083.     while (content != content.top) {
  1084.       var frames = content.parent.frames;
  1085.       for (var i = 0; i < frames.length && frames[i] != content; i++);
  1086.       id = i + "|" + id;
  1087.       content = content.parent;
  1088.     }
  1089.     
  1090.     // mark this element for saving
  1091.     aPanel.__SS_text[ix] = { id: id, element: aTextarea };
  1092.     
  1093.     return true;
  1094.   },
  1095.  
  1096.   /**
  1097.    * go through all tabs and store the current scroll positions
  1098.    * and innerHTML content of WYSIWYG editors
  1099.    * @param aWindow
  1100.    *        Window reference
  1101.    */
  1102.   _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
  1103.     var browsers = aWindow.getBrowser().browsers;
  1104.     for (var i = 0; i < browsers.length; i++) {
  1105.       try {
  1106.         var tabData = this._windows[aWindow.__SSi].tabs[i];
  1107.         if (tabData.entries.length == 0 ||
  1108.             browsers[i].parentNode.__SS_data && browsers[i].parentNode.__SS_data._tab)
  1109.           continue; // ignore incompletely initialized tabs
  1110.         this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
  1111.       }
  1112.       catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
  1113.     }
  1114.   },
  1115.  
  1116.   /**
  1117.    * go through all frames and store the current scroll positions
  1118.    * and innerHTML content of WYSIWYG editors
  1119.    * @param aWindow
  1120.    *        Window reference
  1121.    * @param aBrowser
  1122.    *        single browser reference
  1123.    * @param aTabData
  1124.    *        tabData object to add the information to
  1125.    * @param aFullData
  1126.    *        always return privacy sensitive data (use with care)
  1127.    */
  1128.   _updateTextAndScrollDataForTab:
  1129.     function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
  1130.     var text = [];
  1131.     if (aBrowser.parentNode.__SS_text &&
  1132.         (aFullData || this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https")))) {
  1133.       for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) {
  1134.         var data = aBrowser.parentNode.__SS_text[ix];
  1135.         if (!data.cache)
  1136.           // update the text element's value before adding it to the data structure
  1137.           data.cache = encodeURI(data.element.value);
  1138.         text.push(data.id + "=" + data.cache);
  1139.       }
  1140.     }
  1141.     if (aBrowser.currentURI.spec == "about:config")
  1142.       text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").
  1143.                                                wrappedJSObject.value)];
  1144.     if (text.length > 0)
  1145.       aTabData.text = text.join(" ");
  1146.     else if (aTabData.text)
  1147.       delete aTabData.text;
  1148.     
  1149.     var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
  1150.     // entry data needn't exist for tabs just initialized with an incomplete session state
  1151.     if (aTabData.entries[tabIndex])
  1152.       this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
  1153.                                             aTabData.entries[tabIndex], aFullData);
  1154.   },
  1155.  
  1156.   /**
  1157.    * go through all subframes and store the current scroll positions
  1158.    * and innerHTML content of WYSIWYG editors
  1159.    * @param aWindow
  1160.    *        Window reference
  1161.    * @param aContent
  1162.    *        frame reference
  1163.    * @param aData
  1164.    *        part of a tabData object to add the information to
  1165.    * @param aFullData
  1166.    *        always return privacy sensitive data (use with care)
  1167.    */
  1168.   _updateTextAndScrollDataForFrame:
  1169.     function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aFullData) {
  1170.     for (var i = 0; i < aContent.frames.length; i++) {
  1171.       if (aData.children && aData.children[i])
  1172.         this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i], aFullData);
  1173.     }
  1174.     // designMode is undefined e.g. for XUL documents (as about:config)
  1175.     var isHTTPS = this._getURIFromString((aContent.parent || aContent).
  1176.                                          document.location.href).schemeIs("https");
  1177.     if ((aContent.document.designMode || "") == "on" &&
  1178.         (aFullData || this._checkPrivacyLevel(isHTTPS))) {
  1179.       if (aData.innerHTML === undefined && !aFullData) {
  1180.         // we get no "input" events from iframes - listen for keypress here
  1181.         var _this = this;
  1182.         aContent.addEventListener("keypress", function(aEvent) {
  1183.           _this.saveStateDelayed(aWindow, 3000); }, true);
  1184.       }
  1185.       aData.innerHTML = aContent.document.body.innerHTML;
  1186.     }
  1187.     aData.scroll = aContent.scrollX + "," + aContent.scrollY;
  1188.    },
  1189.  
  1190.   /**
  1191.    * store all hosts for a URL
  1192.    * @param aWindow
  1193.    *        Window reference
  1194.    */
  1195.   _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
  1196.     var hosts = this._windows[aWindow.__SSi]._hosts = {};
  1197.     
  1198.     // get all possible subdomain levels for a given URL
  1199.     var _this = this;
  1200.     function extractHosts(aEntry) {
  1201.       if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url) &&
  1202.         !hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
  1203.         var host = RegExp.$1;
  1204.         var ix;
  1205.         for (ix = host.indexOf(".") + 1; ix; ix = host.indexOf(".", ix) + 1) {
  1206.           hosts[host.substr(ix)] = true;
  1207.         }
  1208.         hosts[host] = true;
  1209.       }
  1210.       else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) {
  1211.         hosts[RegExp.$1] = true;
  1212.       }
  1213.       if (aEntry.children) {
  1214.         aEntry.children.forEach(extractHosts);
  1215.       }
  1216.     }
  1217.     
  1218.     this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
  1219.   },
  1220.  
  1221.   /**
  1222.    * Serialize cookie data
  1223.    * @param aWindows
  1224.    *        array of Window references
  1225.    */
  1226.   _updateCookies: function sss_updateCookies(aWindows) {
  1227.     var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"].
  1228.                       getService(Ci.nsICookieManager).enumerator;
  1229.     // collect the cookies per window
  1230.     for (var i = 0; i < aWindows.length; i++)
  1231.       aWindows[i].cookies = [];
  1232.     
  1233.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1234.     var MAX_EXPIRY = Math.pow(2, 62);
  1235.     while (cookiesEnum.hasMoreElements()) {
  1236.       var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
  1237.       if (cookie.isSession && this._checkPrivacyLevel(cookie.isSecure)) {
  1238.         var jscookie = null;
  1239.         aWindows.forEach(function(aWindow) {
  1240.           if (aWindow._hosts && aWindow._hosts[cookie.rawHost]) {
  1241.             // serialize the cookie when it's first needed
  1242.             if (!jscookie) {
  1243.               jscookie = { host: cookie.host, value: cookie.value };
  1244.               // only add attributes with non-default values (saving a few bits)
  1245.               if (cookie.path) jscookie.path = cookie.path;
  1246.               if (cookie.name) jscookie.name = cookie.name;
  1247.               if (cookie.isSecure) jscookie.secure = true;
  1248.               if (cookie.isHttpOnly) jscookie.httponly = true;
  1249.               if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
  1250.             }
  1251.             aWindow.cookies.push(jscookie);
  1252.           }
  1253.         });
  1254.       }
  1255.     }
  1256.     
  1257.     // don't include empty cookie sections
  1258.     for (i = 0; i < aWindows.length; i++)
  1259.       if (aWindows[i].cookies.length == 0)
  1260.         delete aWindows[i].cookies;
  1261.   },
  1262.  
  1263.   /**
  1264.    * Store window dimensions, visibility, sidebar
  1265.    * @param aWindow
  1266.    *        Window reference
  1267.    */
  1268.   _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) {
  1269.     var winData = this._windows[aWindow.__SSi];
  1270.     
  1271.     WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  1272.       winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
  1273.     }, this);
  1274.     
  1275.     var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
  1276.       return aWindow[aItem] && !aWindow[aItem].visible;
  1277.     });
  1278.     if (hidden.length != 0)
  1279.       winData.hidden = hidden.join(",");
  1280.     else if (winData.hidden)
  1281.       delete winData.hidden;
  1282.  
  1283.     var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
  1284.     if (sidebar)
  1285.       winData.sidebar = sidebar;
  1286.     else if (winData.sidebar)
  1287.       delete winData.sidebar;
  1288.   },
  1289.  
  1290.   /**
  1291.    * serialize session data as Ini-formatted string
  1292.    * @returns string
  1293.    */
  1294.   _getCurrentState: function sss_getCurrentState() {
  1295.     var activeWindow = this._getMostRecentBrowserWindow();
  1296.     
  1297.     if (this._loadState == STATE_RUNNING) {
  1298.       // update the data for all windows with activities since the last save operation
  1299.       this._forEachBrowserWindow(function(aWindow) {
  1300.         if (this._dirty || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
  1301.           this._collectWindowData(aWindow);
  1302.         }
  1303.         else { // always update the window features (whose change alone never triggers a save operation)
  1304.           this._updateWindowFeatures(aWindow);
  1305.         }
  1306.       }, this);
  1307.       this._dirtyWindows = [];
  1308.       this._dirty = false;
  1309.     }
  1310.     
  1311.     // collect the data for all windows
  1312.     var total = [], windows = [];
  1313.     var ix;
  1314.     for (ix in this._windows) {
  1315.       total.push(this._windows[ix]);
  1316.       windows.push(ix);
  1317.     }
  1318.     this._updateCookies(total);
  1319.     
  1320.     // if no browser window remains open, return the state of the last closed window
  1321.     if (total.length == 0 && this._lastWindowClosed) {
  1322.       total.push(this._lastWindowClosed);
  1323.     }
  1324.     if (activeWindow) {
  1325.       this.activeWindowSSiCache = activeWindow.__SSi || "";
  1326.     }
  1327.     ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1;
  1328.  
  1329.     return { windows: total, selectedWindow: ix + 1 };
  1330.   },
  1331.  
  1332.   /**
  1333.    * serialize session data for a window 
  1334.    * @param aWindow
  1335.    *        Window reference
  1336.    * @returns string
  1337.    */
  1338.   _getWindowState: function sss_getWindowState(aWindow) {
  1339.     if (this._loadState == STATE_RUNNING) {
  1340.       this._collectWindowData(aWindow);
  1341.     }
  1342.     
  1343.     var total = [this._windows[aWindow.__SSi]];
  1344.     this._updateCookies(total);
  1345.     
  1346.     return { windows: total };
  1347.   },
  1348.  
  1349.   _collectWindowData: function sss_collectWindowData(aWindow) {
  1350.     // update the internal state data for this window
  1351.     this._saveWindowHistory(aWindow);
  1352.     this._updateTextAndScrollData(aWindow);
  1353.     this._updateCookieHosts(aWindow);
  1354.     this._updateWindowFeatures(aWindow);
  1355.     
  1356.     this._dirtyWindows[aWindow.__SSi] = false;
  1357.   },
  1358.  
  1359. /* ........ Restoring Functionality .............. */
  1360.  
  1361.   /**
  1362.    * restore features to a single window
  1363.    * @param aWindow
  1364.    *        Window reference
  1365.    * @param aState
  1366.    *        JS object or its eval'able source
  1367.    * @param aOverwriteTabs
  1368.    *        bool overwrite existing tabs w/ new ones
  1369.    * @param aFollowUp
  1370.    *        bool this isn't the restoration of the first window
  1371.    */
  1372.   restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
  1373.     if (this._restoreCount) {
  1374.       this._restoreCount--;
  1375.       if (this._restoreCount == 0) {
  1376.         // This was the last window restored at startup, notify observers.
  1377.         var observerService = Cc["@mozilla.org/observer-service;1"].
  1378.                               getService(Ci.nsIObserverService);
  1379.         observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  1380.       }
  1381.     }
  1382.  
  1383.     if (!aFollowUp) {
  1384.       this.windowToFocus = aWindow;
  1385.     }
  1386.     // initialize window if necessary
  1387.     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  1388.       this.onLoad(aWindow);
  1389.  
  1390.     try {
  1391.       var root = typeof aState == "string" ? this._safeEval(aState) : aState;
  1392.       if (!root.windows[0]) {
  1393.         return; // nothing to restore
  1394.       }
  1395.     }
  1396.     catch (ex) { // invalid state object - don't restore anything 
  1397.       debug(ex);
  1398.       return;
  1399.     }
  1400.     
  1401.     var winData;
  1402.     if (!aState.selectedWindow) {
  1403.       aState.selectedWindow = 0;
  1404.     }
  1405.     // open new windows for all further window entries of a multi-window session
  1406.     // (unless they don't contain any tab data)
  1407.     for (var w = 1; w < root.windows.length; w++) {
  1408.       winData = root.windows[w];
  1409.       if (winData && winData.tabs && winData.tabs[0]) {
  1410.         var window = this._openWindowWithState({ windows: [winData] });
  1411.         if (w == aState.selectedWindow - 1) {
  1412.           this.windowToFocus = window;
  1413.         }
  1414.       }
  1415.     }
  1416.     winData = root.windows[0];
  1417.     if (!winData.tabs) {
  1418.       winData.tabs = [];
  1419.     }
  1420.     
  1421.     var tabbrowser = aWindow.getBrowser();
  1422.     var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
  1423.     var newTabCount = winData.tabs.length;
  1424.     
  1425.     for (var t = 0; t < newTabCount; t++) {
  1426.       winData.tabs[t]._tab = t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab();
  1427.       // when resuming at startup: add additionally requested pages to the end
  1428.       if (!aOverwriteTabs && root._firstTabs) {
  1429.         tabbrowser.moveTabTo(winData.tabs[t]._tab, t);
  1430.       }
  1431.     }
  1432.  
  1433.     // when overwriting tabs, remove all superflous ones
  1434.     for (t = openTabCount - 1; t >= newTabCount; t--) {
  1435.       tabbrowser.removeTab(tabbrowser.mTabs[t]);
  1436.     }
  1437.     
  1438.     if (aOverwriteTabs) {
  1439.       this.restoreWindowFeatures(aWindow, winData);
  1440.     }
  1441.     if (winData.cookies) {
  1442.       this.restoreCookies(winData.cookies);
  1443.     }
  1444.     if (winData.extData) {
  1445.       if (!this._windows[aWindow.__SSi].extData) {
  1446.         this._windows[aWindow.__SSi].extData = {}
  1447.       }
  1448.       for (var key in winData.extData) {
  1449.         this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
  1450.       }
  1451.     }
  1452.     if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
  1453.       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs;
  1454.     }
  1455.     
  1456.     this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
  1457.       (parseInt(winData.selected) || 1) : 0), 0, 0);
  1458.   },
  1459.  
  1460.   /**
  1461.    * Manage history restoration for a window
  1462.    * @param aTabs
  1463.    *        Array of tab data
  1464.    * @param aCurrentTabs
  1465.    *        Array of tab references
  1466.    * @param aSelectTab
  1467.    *        Index of selected tab
  1468.    * @param aIx
  1469.    *        Index of the next tab to check readyness for
  1470.    * @param aCount
  1471.    *        Counter for number of times delaying b/c browser or history aren't ready
  1472.    */
  1473.   restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount) {
  1474.     var tabbrowser = aWindow.getBrowser();
  1475.     
  1476.     // make sure that all browsers and their histories are available
  1477.     // - if one's not, resume this check in 100ms (repeat at most 10 times)
  1478.     for (var t = aIx; t < aTabs.length; t++) {
  1479.       try {
  1480.         if (!tabbrowser.getBrowserForTab(aTabs[t]._tab).webNavigation.sessionHistory) {
  1481.           throw new Error();
  1482.         }
  1483.       }
  1484.       catch (ex) { // in case browser or history aren't ready yet 
  1485.         if (aCount < 10) {
  1486.           var restoreHistoryFunc = function(self) {
  1487.             self.restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount + 1);
  1488.           }
  1489.           aWindow.setTimeout(restoreHistoryFunc, 100, this);
  1490.           return;
  1491.         }
  1492.       }
  1493.     }
  1494.     
  1495.     // mark the tabs as loading
  1496.     for (t = 0; t < aTabs.length; t++) {
  1497.       if (!aTabs[t].entries || !aTabs[t].entries[0])
  1498.         continue; // there won't be anything to load
  1499.       
  1500.       var tab = aTabs[t]._tab;
  1501.       var browser = tabbrowser.getBrowserForTab(tab);
  1502.       browser.stop(); // in case about:blank isn't done yet
  1503.       
  1504.       tab.setAttribute("busy", "true");
  1505.       tabbrowser.updateIcon(tab);
  1506.       tabbrowser.setTabTitleLoading(tab);
  1507.       
  1508.       // keep the data around to prevent dataloss in case
  1509.       // a tab gets closed before it's been properly restored
  1510.       browser.parentNode.__SS_data = aTabs[t];
  1511.     }
  1512.     
  1513.     // make sure to restore the selected tab first (if any)
  1514.     if (aSelectTab-- && aTabs[aSelectTab]) {
  1515.         aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
  1516.         tabbrowser.selectedTab = aTabs[0]._tab;
  1517.     }
  1518.  
  1519.     // helper hash for ensuring unique frame IDs
  1520.     var idMap = { used: {} };
  1521.     this.restoreHistory(aWindow, aTabs, idMap);
  1522.   },
  1523.  
  1524.   /**
  1525.    * Restory history for a window
  1526.    * @param aWindow
  1527.    *        Window reference
  1528.    * @param aTabs
  1529.    *        Array of tab data
  1530.    * @param aIdMap
  1531.    *        Hash for ensuring unique frame IDs
  1532.    */
  1533.   restoreHistory: function sss_restoreHistory(aWindow, aTabs, aIdMap) {
  1534.     var _this = this;
  1535.     while (aTabs.length > 0 && (!aTabs[0]._tab || !aTabs[0]._tab.parentNode)) {
  1536.       aTabs.shift(); // this tab got removed before being completely restored
  1537.     }
  1538.     if (aTabs.length == 0) {
  1539.       return; // no more tabs to restore
  1540.     }
  1541.     
  1542.     var tabData = aTabs.shift();
  1543.  
  1544.     var tab = tabData._tab;
  1545.     var browser = aWindow.getBrowser().getBrowserForTab(tab);
  1546.     var history = browser.webNavigation.sessionHistory;
  1547.     
  1548.     if (history.count > 0) {
  1549.       history.PurgeHistory(history.count);
  1550.     }
  1551.     history.QueryInterface(Ci.nsISHistoryInternal);
  1552.     
  1553.     if (!tabData.entries) {
  1554.       tabData.entries = [];
  1555.     }
  1556.     if (tabData.extData) {
  1557.       tab.__SS_extdata = tabData.extData;
  1558.     }
  1559.     
  1560.     for (var i = 0; i < tabData.entries.length; i++) {
  1561.       history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
  1562.     }
  1563.     
  1564.     // make sure to reset the capabilities and attributes, in case this tab gets reused
  1565.     var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
  1566.     CAPABILITIES.forEach(function(aCapability) {
  1567.       browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
  1568.     });
  1569.     Array.filter(tab.attributes, function(aAttr) {
  1570.       return (_this.xulAttributes.indexOf(aAttr.name) > -1);
  1571.     }).forEach(tab.removeAttribute, tab);
  1572.     if (tabData.xultab) {
  1573.       tabData.xultab.split(" ").forEach(function(aAttr) {
  1574.         if (/^([^\s=]+)=(.*)/.test(aAttr)) {
  1575.           tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2));
  1576.         }
  1577.       });
  1578.     }
  1579.     
  1580.     // notify the tabbrowser that the tab chrome has been restored
  1581.     var event = aWindow.document.createEvent("Events");
  1582.     event.initEvent("SSTabRestoring", true, false);
  1583.     tab.dispatchEvent(event);
  1584.     
  1585.     var activeIndex = (tabData.index || tabData.entries.length) - 1;
  1586.     try {
  1587.       browser.webNavigation.gotoIndex(activeIndex);
  1588.     }
  1589.     catch (ex) { } // ignore an invalid tabData.index
  1590.     
  1591.     // restore those aspects of the currently active documents
  1592.     // which are not preserved in the plain history entries
  1593.     // (mainly scroll state and text data)
  1594.     browser.__SS_restore_data = tabData.entries[activeIndex] || {};
  1595.     browser.__SS_restore_text = tabData.text || "";
  1596.     browser.__SS_restore_tab = tab;
  1597.     browser.__SS_restore = this.restoreDocument_proxy;
  1598.     browser.addEventListener("load", browser.__SS_restore, true);
  1599.     
  1600.     aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aIdMap); }, 0);
  1601.   },
  1602.  
  1603.   /**
  1604.    * expands serialized history data into a session-history-entry instance
  1605.    * @param aEntry
  1606.    *        Object containing serialized history data for a URL
  1607.    * @param aIdMap
  1608.    *        Hash for ensuring unique frame IDs
  1609.    * @returns nsISHEntry
  1610.    */
  1611.   _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
  1612.     var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
  1613.                   createInstance(Ci.nsISHEntry);
  1614.     
  1615.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1616.                     getService(Ci.nsIIOService);
  1617.     shEntry.setURI(ioService.newURI(aEntry.url, null, null));
  1618.     shEntry.setTitle(aEntry.title || aEntry.url);
  1619.     if (aEntry.subframe)
  1620.       shEntry.setIsSubFrame(aEntry.subframe || false);
  1621.     shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
  1622.     if (aEntry.contentType)
  1623.       shEntry.contentType = aEntry.contentType;
  1624.     
  1625.     if (aEntry.cacheKey) {
  1626.       var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
  1627.                      createInstance(Ci.nsISupportsPRUint32);
  1628.       cacheKey.data = aEntry.cacheKey;
  1629.       shEntry.cacheKey = cacheKey;
  1630.     }
  1631.  
  1632.     if (aEntry.ID) {
  1633.       // get a new unique ID for this frame (since the one from the last
  1634.       // start might already be in use)
  1635.       var id = aIdMap[aEntry.ID] || 0;
  1636.       if (!id) {
  1637.         for (id = Date.now(); aIdMap.used[id]; id++);
  1638.         aIdMap[aEntry.ID] = id;
  1639.         aIdMap.used[id] = true;
  1640.       }
  1641.       shEntry.ID = id;
  1642.     }
  1643.     
  1644.     if (aEntry.scroll) {
  1645.       var scrollPos = (aEntry.scroll || "0,0").split(",");
  1646.       scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
  1647.       shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
  1648.     }
  1649.  
  1650.     var postdata;
  1651.     if (aEntry.postdata_b64) {  // Firefox 3
  1652.       postdata = atob(aEntry.postdata_b64);
  1653.     } else if (aEntry.postdata) { // Firefox 2
  1654.       postdata = aEntry.postdata;
  1655.     }
  1656.  
  1657.     if (postdata) {
  1658.       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
  1659.                    createInstance(Ci.nsIStringInputStream);
  1660.       stream.setData(postdata, postdata.length);
  1661.       shEntry.postData = stream;
  1662.     }
  1663.  
  1664.     if (aEntry.owner_b64) {  // Firefox 3
  1665.       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
  1666.                        createInstance(Ci.nsIStringInputStream);
  1667.       var binaryData = atob(aEntry.owner_b64);
  1668.       ownerInput.setData(binaryData, binaryData.length);
  1669.       var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
  1670.                          createInstance(Ci.nsIObjectInputStream);
  1671.       binaryStream.setInputStream(ownerInput);
  1672.       try { // Catch possible deserialization exceptions
  1673.         shEntry.owner = binaryStream.readObject(true);
  1674.       } catch (ex) { debug(ex); }
  1675.     } else if (aEntry.ownerURI) { // Firefox 2
  1676.       var uriObj = ioService.newURI(aEntry.ownerURI, null, null);
  1677.       shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1678.                       getService(Ci.nsIScriptSecurityManager).
  1679.                       getCodebasePrincipal(uriObj);
  1680.     }
  1681.     
  1682.     if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
  1683.       for (var i = 0; i < aEntry.children.length; i++) {
  1684.         shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
  1685.       }
  1686.     }
  1687.     
  1688.     return shEntry;
  1689.   },
  1690.  
  1691.   /**
  1692.    * Restore properties to a loaded document
  1693.    */
  1694.   restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) {
  1695.     // wait for the top frame to be loaded completely
  1696.     if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
  1697.       return;
  1698.     }
  1699.     
  1700.     var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
  1701.     function restoreTextData(aContent, aPrefix) {
  1702.       textArray.forEach(function(aEntry) {
  1703.         if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) && (!RegExp.$1 || RegExp.$1 == aPrefix)) {
  1704.           var document = aContent.document;
  1705.           var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null;
  1706.           if (node && "value" in node) {
  1707.             node.value = decodeURI(RegExp.$4);
  1708.             
  1709.             var event = document.createEvent("UIEvents");
  1710.             event.initUIEvent("input", true, true, aContent, 0);
  1711.             node.dispatchEvent(event);
  1712.           }
  1713.         }
  1714.       });
  1715.     }
  1716.     
  1717.     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
  1718.       restoreTextData(aContent, aPrefix);
  1719.       if (aData.innerHTML) {
  1720.         aContent.setTimeout(function(aHTML) { if (this.document.designMode == "on") { this.document.body.innerHTML = aHTML; } }, 0, aData.innerHTML);
  1721.       }
  1722.       if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) {
  1723.         aContent.scrollTo(RegExp.$1, RegExp.$2);
  1724.       }
  1725.       for (var i = 0; i < aContent.frames.length; i++) {
  1726.         if (aData.children && aData.children[i]) {
  1727.           restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], i + "|" + aPrefix);
  1728.         }
  1729.       }
  1730.     }
  1731.     
  1732.     var content = aEvent.originalTarget.defaultView;
  1733.     if (this.currentURI.spec == "about:config") {
  1734.       // unwrap the document for about:config because otherwise the properties
  1735.       // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
  1736.       content = content.wrappedJSObject;
  1737.     }
  1738.     restoreTextDataAndScrolling(content, this.__SS_restore_data, "");
  1739.     
  1740.     // notify the tabbrowser that this document has been completely restored
  1741.     var event = this.ownerDocument.createEvent("Events");
  1742.     event.initEvent("SSTabRestored", true, false);
  1743.     this.__SS_restore_tab.dispatchEvent(event);
  1744.     
  1745.     this.removeEventListener("load", this.__SS_restore, true);
  1746.     delete this.__SS_restore_data;
  1747.     delete this.__SS_restore_text;
  1748.     delete this.__SS_restore_tab;
  1749.     delete this.__SS_restore;
  1750.   },
  1751.  
  1752.   /**
  1753.    * Restore visibility and dimension features to a window
  1754.    * @param aWindow
  1755.    *        Window reference
  1756.    * @param aWinData
  1757.    *        Object containing session data for the window
  1758.    */
  1759.   restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) {
  1760.     var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
  1761.     WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
  1762.       aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
  1763.     });
  1764.     
  1765.     var _this = this;
  1766.     aWindow.setTimeout(function() {
  1767.       _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0, 
  1768.         aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN,
  1769.         "screenY" in aWinData ? aWinData.screenY : NaN,
  1770.         aWinData.sizemode || "", aWinData.sidebar || ""]);
  1771.     }, 0);
  1772.   },
  1773.  
  1774.   /**
  1775.    * Restore a window's dimensions
  1776.    * @param aWidth
  1777.    *        Window width
  1778.    * @param aHeight
  1779.    *        Window height
  1780.    * @param aLeft
  1781.    *        Window left
  1782.    * @param aTop
  1783.    *        Window top
  1784.    * @param aSizeMode
  1785.    *        Window size mode (eg: maximized)
  1786.    * @param aSidebar
  1787.    *        Sidebar command
  1788.    */
  1789.   restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
  1790.     var win = aWindow;
  1791.     var _this = this;
  1792.     function win_(aName) { return _this._getWindowDimension(win, aName); }
  1793.     
  1794.     // only modify those aspects which aren't correct yet
  1795.     if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
  1796.       aWindow.resizeTo(aWidth, aHeight);
  1797.     }
  1798.     if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
  1799.       aWindow.moveTo(aLeft, aTop);
  1800.     }
  1801.     if (aSizeMode == "maximized" && win_("sizemode") != "maximized") {
  1802.       aWindow.maximize();
  1803.     }
  1804.     else if (aSizeMode && aSizeMode != "maximized" && win_("sizemode") != "normal") {
  1805.       aWindow.restore();
  1806.     }
  1807.     var sidebar = aWindow.document.getElementById("sidebar-box");
  1808.     if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
  1809.       aWindow.toggleSidebar(aSidebar);
  1810.     }
  1811.     // since resizing/moving a window brings it to the foreground,
  1812.     // we might want to re-focus the last focused window
  1813.     if (this.windowToFocus) {
  1814.       this.windowToFocus.focus();
  1815.     }
  1816.   },
  1817.  
  1818.   /**
  1819.    * Restores cookies (accepting both Firefox 2.0 and current format)
  1820.    * @param aCookies
  1821.    *        Array of cookie objects
  1822.    */
  1823.   restoreCookies: function sss_restoreCookies(aCookies) {
  1824.     if (aCookies.count && aCookies.domain1) {
  1825.       // convert to the new cookie serialization format
  1826.       var converted = [];
  1827.       for (var i = 1; i <= aCookies.count; i++) {
  1828.         // for simplicity we only accept the format we produced ourselves
  1829.         var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/);
  1830.         if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i]))
  1831.           converted.push({
  1832.             host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2],
  1833.             secure: parsed[4], httponly: parsed[5]
  1834.           });
  1835.       }
  1836.       aCookies = converted;
  1837.     }
  1838.     
  1839.     var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
  1840.                         getService(Ci.nsICookieManager2);
  1841.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1842.     var MAX_EXPIRY = Math.pow(2, 62);
  1843.     for (i = 0; i < aCookies.length; i++) {
  1844.       var cookie = aCookies[i];
  1845.       try {
  1846.         cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
  1847.       }
  1848.       catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
  1849.     }
  1850.   },
  1851.  
  1852. /* ........ Disk Access .............. */
  1853.  
  1854.   /**
  1855.    * save state delayed by N ms
  1856.    * marks window as dirty (i.e. data update can't be skipped)
  1857.    * @param aWindow
  1858.    *        Window reference
  1859.    * @param aDelay
  1860.    *        Milliseconds to delay
  1861.    */
  1862.   saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) {
  1863.     if (aWindow) {
  1864.       this._dirtyWindows[aWindow.__SSi] = true;
  1865.     }
  1866.  
  1867.     if (!this._saveTimer && this._resume_from_crash) {
  1868.       // interval until the next disk operation is allowed
  1869.       var minimalDelay = this._lastSaveTime + this._interval - Date.now();
  1870.       
  1871.       // if we have to wait, set a timer, otherwise saveState directly
  1872.       aDelay = Math.max(minimalDelay, aDelay || 2000);
  1873.       if (aDelay > 0) {
  1874.         this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1875.         this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
  1876.       }
  1877.       else {
  1878.         this.saveState();
  1879.       }
  1880.     }
  1881.   },
  1882.  
  1883.   /**
  1884.    * save state to disk
  1885.    * @param aUpdateAll
  1886.    *        Bool update all windows 
  1887.    */
  1888.   saveState: function sss_saveState(aUpdateAll) {
  1889.     // if crash recovery is disabled, only save session resuming information
  1890.     if (!this._resume_from_crash && this._loadState == STATE_RUNNING)
  1891.       return;
  1892.     
  1893.     this._dirty = aUpdateAll;
  1894.     var oState = this._getCurrentState();
  1895.     oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
  1896.     this._writeFile(this._sessionFile, oState.toSource());
  1897.     this._lastSaveTime = Date.now();
  1898.   },
  1899.  
  1900.   /**
  1901.    * delete session datafile and backup
  1902.    */
  1903.   _clearDisk: function sss_clearDisk() {
  1904.     if (this._sessionFile.exists()) {
  1905.       try {
  1906.         this._sessionFile.remove(false);
  1907.       }
  1908.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1909.     }
  1910.     if (this._sessionFileBackup.exists()) {
  1911.       try {
  1912.         this._sessionFileBackup.remove(false);
  1913.       }
  1914.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1915.     }
  1916.   },
  1917.  
  1918. /* ........ Auxiliary Functions .............. */
  1919.  
  1920.   /**
  1921.    * call a callback for all currently opened browser windows
  1922.    * (might miss the most recent one)
  1923.    * @param aFunc
  1924.    *        Callback each window is passed to
  1925.    */
  1926.   _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
  1927.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1928.                          getService(Ci.nsIWindowMediator);
  1929.     var windowsEnum = windowMediator.getEnumerator("navigator:browser");
  1930.     
  1931.     while (windowsEnum.hasMoreElements()) {
  1932.       var window = windowsEnum.getNext();
  1933.       if (window.__SSi) {
  1934.         aFunc.call(this, window);
  1935.       }
  1936.     }
  1937.   },
  1938.  
  1939.   /**
  1940.    * Returns most recent window
  1941.    * @returns Window reference
  1942.    */
  1943.   _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
  1944.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1945.                          getService(Ci.nsIWindowMediator);
  1946.     return windowMediator.getMostRecentWindow("navigator:browser");
  1947.   },
  1948.  
  1949.   /**
  1950.    * open a new browser window for a given session state
  1951.    * called when restoring a multi-window session
  1952.    * @param aState
  1953.    *        Object containing session data
  1954.    */
  1955.   _openWindowWithState: function sss_openWindowWithState(aState) {
  1956.     var argString = Cc["@mozilla.org/supports-string;1"].
  1957.                     createInstance(Ci.nsISupportsString);
  1958.     argString.data = "";
  1959.  
  1960.     //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)?
  1961.     var window = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1962.                  getService(Ci.nsIWindowWatcher).
  1963.                  openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank",
  1964.                             "chrome,dialog=no,all", argString);
  1965.     
  1966.     window.__SS_state = aState;
  1967.     var _this = this;
  1968.     window.addEventListener("load", function(aEvent) {
  1969.       aEvent.currentTarget.removeEventListener("load", arguments.callee, true);
  1970.       _this.restoreWindow(aEvent.currentTarget, aEvent.currentTarget.__SS_state, true, true);
  1971.       delete aEvent.currentTarget.__SS_state;
  1972.     }, true);
  1973.     
  1974.     return window;
  1975.   },
  1976.  
  1977.   /**
  1978.    * Whether or not to resume session, if not recovering from a crash.
  1979.    * @returns bool
  1980.    */
  1981.   _doResumeSession: function sss_doResumeSession() {
  1982.     return this._prefBranch.getIntPref("startup.page") == 3 ||
  1983.       this._prefBranch.getBoolPref("sessionstore.resume_session_once");
  1984.   },
  1985.  
  1986.   /**
  1987.    * whether the user wants to load any other page at startup
  1988.    * (except the homepage) - needed for determining whether to overwrite the current tabs
  1989.    * C.f.: nsBrowserContentHandler's defaultArgs implementation.
  1990.    * @returns bool
  1991.    */
  1992.   _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) {
  1993.     var defaultArgs = Cc["@mozilla.org/browser/clh;1"].
  1994.                       getService(Ci.nsIBrowserHandler).defaultArgs;
  1995.     if (aWindow.arguments && aWindow.arguments[0] &&
  1996.         aWindow.arguments[0] == defaultArgs)
  1997.       aWindow.arguments[0] = null;
  1998.  
  1999.     return !aWindow.arguments || !aWindow.arguments[0];
  2000.   },
  2001.  
  2002.   /**
  2003.    * don't save sensitive data if the user doesn't want to
  2004.    * (distinguishes between encrypted and non-encrypted sites)
  2005.    * @param aIsHTTPS
  2006.    *        Bool is encrypted
  2007.    * @returns bool
  2008.    */
  2009.   _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
  2010.     return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
  2011.   },
  2012.  
  2013.   /**
  2014.    * on popup windows, the XULWindow's attributes seem not to be set correctly
  2015.    * we use thus JSDOMWindow attributes for sizemode and normal window attributes
  2016.    * (and hope for reasonable values when maximized/minimized - since then
  2017.    * outerWidth/outerHeight aren't the dimensions of the restored window)
  2018.    * @param aWindow
  2019.    *        Window reference
  2020.    * @param aAttribute
  2021.    *        String sizemode | width | height | other window attribute
  2022.    * @returns string
  2023.    */
  2024.   _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) {
  2025.     if (aAttribute == "sizemode") {
  2026.       switch (aWindow.windowState) {
  2027.       case aWindow.STATE_MAXIMIZED:
  2028.         return "maximized";
  2029.       case aWindow.STATE_MINIMIZED:
  2030.         return "minimized";
  2031.       default:
  2032.         return "normal";
  2033.       }
  2034.     }
  2035.     
  2036.     var dimension;
  2037.     switch (aAttribute) {
  2038.     case "width":
  2039.       dimension = aWindow.outerWidth;
  2040.       break;
  2041.     case "height":
  2042.       dimension = aWindow.outerHeight;
  2043.       break;
  2044.     default:
  2045.       dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
  2046.       break;
  2047.     }
  2048.     
  2049.     if (aWindow.windowState == aWindow.STATE_NORMAL) {
  2050.       return dimension;
  2051.     }
  2052.     return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
  2053.   },
  2054.  
  2055.   /**
  2056.    * Convenience method to get localized string bundles
  2057.    * @param aURI
  2058.    * @returns nsIStringBundle
  2059.    */
  2060.   _getStringBundle: function sss_getStringBundle(aURI) {
  2061.      var bundleService = Cc["@mozilla.org/intl/stringbundle;1"].
  2062.                          getService(Ci.nsIStringBundleService);
  2063.      var appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
  2064.                      getService(Ci.nsILocaleService).getApplicationLocale();
  2065.      return bundleService.createBundle(aURI, appLocale);
  2066.   },
  2067.  
  2068.   /**
  2069.    * Get nsIURI from string
  2070.    * @param string
  2071.    * @returns nsIURI
  2072.    */
  2073.   _getURIFromString: function sss_getURIFromString(aString) {
  2074.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  2075.                     getService(Ci.nsIIOService);
  2076.     return ioService.newURI(aString, null, null);
  2077.   },
  2078.  
  2079.   /**
  2080.    * Annotate a breakpad crash report with the currently selected tab's URL.
  2081.    */
  2082.   _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) {
  2083.     if (!Ci.nsICrashReporter) {
  2084.       // if breakpad isn't built, don't bother next time at all
  2085.       this._updateCrashReportURL = function(aWindow) {};
  2086.       return;
  2087.     }
  2088.     try {
  2089.       var currentUrl = aWindow.getBrowser().currentURI.spec;
  2090.       var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter);
  2091.       cr.annotateCrashReport("URL", currentUrl);
  2092.     }
  2093.     catch (ex) {
  2094.       // don't make noise when crashreporter is built but not enabled
  2095.       if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
  2096.         debug(ex);
  2097.     }
  2098.   },
  2099.  
  2100.   /**
  2101.    * safe eval'ing
  2102.    */
  2103.   _safeEval: function sss_safeEval(aStr) {
  2104.     return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
  2105.   },
  2106.  
  2107.   /**
  2108.    * Converts a JavaScript object into a JSON string
  2109.    * (see http://www.json.org/ for more information).
  2110.    *
  2111.    * The inverse operation consists of eval("(" + JSON_string + ")");
  2112.    * and should be provably safe.
  2113.    *
  2114.    * @param aJSObject is the object to be converted
  2115.    * @return the object's JSON representation
  2116.    */
  2117.   _toJSONString: function sss_toJSONString(aJSObject) {
  2118.     var str = JSON.toString(aJSObject, ["_tab", "_hosts"] /* keys to drop */);
  2119.     
  2120.     // sanity check - so that API consumers can just eval this string
  2121.     if (!JSON.isMostlyHarmless(str))
  2122.       throw new Error("JSON conversion failed unexpectedly!");
  2123.     
  2124.     return str;
  2125.   },
  2126.  
  2127. /* ........ Storage API .............. */
  2128.  
  2129.   /**
  2130.    * write file to disk
  2131.    * @param aFile
  2132.    *        nsIFile
  2133.    * @param aData
  2134.    *        String data
  2135.    */
  2136.   _writeFile: function sss_writeFile(aFile, aData) {
  2137.     // init stream
  2138.     var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  2139.                  createInstance(Ci.nsIFileOutputStream);
  2140.     stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
  2141.  
  2142.     // convert to UTF-8
  2143.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  2144.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  2145.     converter.charset = "UTF-8";
  2146.     var convertedData = converter.ConvertFromUnicode(aData);
  2147.     convertedData += converter.Finish();
  2148.  
  2149.     // write and close stream
  2150.     stream.write(convertedData, convertedData.length);
  2151.     if (stream instanceof Ci.nsISafeOutputStream) {
  2152.       stream.finish();
  2153.     } else {
  2154.       stream.close();
  2155.     }
  2156.   }
  2157. };
  2158.  
  2159. function NSGetModule(aComMgr, aFileSpec)
  2160.   XPCOMUtils.generateModule([SessionStoreService]);
  2161.